﻿using System;
using System.Collections.Generic;
using System.Linq;
using snat.model;
using QuickGraph;

namespace gui {

    /// <summary>
    /// Network View Model class controls the base graph that the
    /// graph UI component binds to
    /// </summary>
    public sealed class NetworkViewModel : IMutableBidirectionalGraph<object, IEdge<object>> {

        public static event guiFixLayoutEvent RelayoutEvent;

        private static volatile NetworkModelConnect connect;
        public static NetworkModelConnect Connect {
            get {
                if (connect == null) connect = new NetworkModelConnect();
                return connect;
            }
        }

        private static volatile NetworkViewModel instance;
        public static NetworkViewModel Instance {
            get {
                if (instance == null) {
                    instance = new NetworkViewModel(NetworkModelConnect.Instance.Nodes, NetworkModelConnect.Instance.Edges);
                }
                return instance;
            }
        }

        // Queues of nodes and edges waiting to be added
        public static List<BaseNode> nodeQueue = new List<BaseNode>();
        public static List<Edge> edgeQueue = new List<Edge>();
        private bool printDebugs = false;

        private List<Edge> edges;
        private Dictionary<int, BaseNode> nodes;

        public NetworkViewModel(Dictionary<int, BaseNode> _nodes, List<Edge> _edges) {
            this.edges = _edges;
            this.nodes = _nodes;
        }

        public event VertexAction<object> VertexRemoved;
        public void RemoveVertex(BaseNode vertex) {
            if (nodeQueue.Contains(vertex)) nodeQueue.Remove(vertex);
            Instance.VertexRemoved(vertex);
            if(printDebugs) Console.WriteLine("ViewMod :: Removing Vert " + vertex.Name);
        }
        public event VertexAction<object> VertexAdded;
        public void AddVertex(BaseNode vertex) {
            nodeQueue.Add(vertex);
            if(printDebugs) Console.WriteLine("ViewMod :: Enqueuing Vert " + vertex.Name);
        }

        public event EdgeAction<object, IEdge<object>> EdgeRemoved;
        public void RemoveEdge(Edge edge) {
            if (edgeQueue.Contains(edge)) edgeQueue.Remove(edge);
            Instance.EdgeRemoved(edge);
            if(printDebugs) Console.WriteLine("ViewMod :: Removing Edge " + edge.Source.Name + " >> " + edge.Target.Name);
        }
        public event EdgeAction<object, IEdge<object>> EdgeAdded;
        public void AddEdge(Edge edge) {
            edgeQueue.Add(edge);
            if(printDebugs) Console.WriteLine("ViewMod :: Enqueuing Edge " + edge.Source.Name + " >> " + edge.Target.Name);
        }

        public static void ProcessGraph() {
            if (nodeQueue.Count > 0) {
                foreach (BaseNode n in nodeQueue) {
                    if (n != null) {   // this is fine for normal
                        Instance.VertexAdded(n);
                    }
                }       
            }
            if (edgeQueue.Count > 0) {
                foreach (Edge e in edgeQueue) {
                    if (Instance.nodes.ContainsKey(e.Source.ID) && Instance.nodes.ContainsKey(e.Target.ID)) {
                        Instance.EdgeAdded(e);
                    }
                }
                edgeQueue.Clear();
            }

            // If we just added new node(s), perform a relayout 
            if (nodeQueue.Count > 0) {
                if (RelayoutEvent != null) RelayoutEvent();
                nodeQueue.Clear();
            }
        }

        public void ClearEdges(object v) {
            throw new NotImplementedException();
        }

        public void ClearInEdges(object v) {
            throw new NotImplementedException();
        }

        public int RemoveInEdgeIf(object v, EdgePredicate<object, IEdge<object>> edgePredicate) {
            throw new NotImplementedException();
        }

        public void ClearOutEdges(object v) {
            throw new NotImplementedException();
        }

        public int RemoveOutEdgeIf(object v, EdgePredicate<object, IEdge<object>> predicate) {
            throw new NotImplementedException();
        }

        public void TrimEdgeExcess() {
            throw new NotImplementedException();
        }

        public void Clear() {
            throw new NotImplementedException();
        }

        public bool AllowParallelEdges {
            get { return false; }
        }

        public bool IsDirected {
            get { return true; }
        }

        public bool ContainsEdge(object source, object target) {
            return edges.Exists(e => e.Source.Equals(source as BaseNode) && e.Target.Equals(target as BaseNode));
        }

        public bool TryGetEdge(object source, object target, out IEdge<object> edge) {
            edge = edges.Find(e => e.Source.Equals(source as BaseNode) && e.Target.Equals(target as BaseNode));
            return edge.Equals(null);
        }

        public bool TryGetEdges(object source, object target, out IEnumerable<IEdge<object>> edges) {
            edges = this.edges.FindAll(e => e.Source.Equals(source as BaseNode) && e.Target.Equals(target as BaseNode));
            return edges.Count() > 0;
        }

        public bool IsOutEdgesEmpty(object v) {
            return (v as BaseNode).OutEdges.Count == 0;
        }

        public int OutDegree(object v) {
            return (v as BaseNode).OutDegree;
        }

        public IEdge<object> OutEdge(object v, int index) {
            return (v as BaseNode).OutEdges[index];
        }

        public IEnumerable<IEdge<object>> OutEdges(object v) {
            return (v as BaseNode).OutEdges.Cast<IEdge<object>>();
        }

        public bool TryGetOutEdges(object v, out IEnumerable<IEdge<object>> edges) {
            edges = (v as BaseNode).OutEdges;
            return edges.Count() > 0;
        }

        public bool ContainsVertex(object vertex) {
            return this.nodes.Values.Cast<object>().Contains(vertex);
        }

        public bool ContainsVertex(int id) {
            return this.nodes.ContainsKey(id);
        }

        public bool AddVertex(object v) {
            throw new NotImplementedException();
        }

        public int AddVertexRange(IEnumerable<object> vertices) {
            throw new NotImplementedException();
        }

        public bool RemoveVertex(object v) {
            throw new NotImplementedException();
        }

        public int RemoveVertexIf(VertexPredicate<object> pred) {
            throw new NotImplementedException();
        }

        public bool IsVerticesEmpty {
            get { return nodes.Count == 0; }
        }

        public int VertexCount {
            get { return nodes.Count; }
        }

        public IEnumerable<object> Vertices {
            get { return this.nodes.Values.Cast<object>(); }
        }

        public bool AddVerticesAndEdge(IEdge<object> edge) {
            throw new NotImplementedException();
        }

        public int AddVerticesAndEdgeRange(IEnumerable<IEdge<object>> edges) {
            throw new NotImplementedException();
        }

        public bool AddEdge(IEdge<object> edge) {
            throw new NotImplementedException();
        }

        public int AddEdgeRange(IEnumerable<IEdge<object>> edges) {
            throw new NotImplementedException();
        }

        public bool RemoveEdge(IEdge<object> edge) {
            throw new NotImplementedException();
        }

        public int RemoveEdgeIf(EdgePredicate<object, IEdge<object>> predicate) {
            throw new NotImplementedException();
        }

        public bool ContainsEdge(IEdge<object> edge) {
            return edges.Contains(edge);
        }

        public int EdgeCount {
            get { return edges.Count; }
        }

        public IEnumerable<IEdge<object>> Edges {
            get { return this.edges.Cast<IEdge<object>>(); }
        }

        public bool IsEdgesEmpty {
            get { return edges.Count == 0; }
        }

        public int Degree(object v) {
            return (v as BaseNode).Degree;
        }

        public int InDegree(object v) {
            return (v as BaseNode).InDegree;
        }

        public IEdge<object> InEdge(object v, int index) {
            return (v as BaseNode).InEdges[index];
        }

        public IEnumerable<IEdge<object>> InEdges(object v) {
            return (v as BaseNode).InEdges.Cast<IEdge<object>>();
        }

        public bool IsInEdgesEmpty(object v) {
            return (v as BaseNode).InDegree == 0;
        }

        public bool TryGetInEdges(object v, out IEnumerable<IEdge<object>> edges) {
            edges = (v as BaseNode).InEdges;
            return edges.Count() > 0;
        }

        internal void QueueProgress() {
            Console.WriteLine("ViewMod :: " + nodeQueue.Count + " new nodes and " + edgeQueue.Count + " new edges waiting in Queue.");
        }
    }
}